package com.cg.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Configuration
public class MyAuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Autowired
    private MyTokenEnhancer myTokenEnhancer;

    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService); //客户端详情服务
        services.setSupportRefreshToken(true);  //允许令牌自动刷新
        services.setTokenStore(tokenStore); //令牌存储策略-内存
//        services.setTokenEnhancer(jwtAccessTokenConverter); //使用jwt token
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(myTokenEnhancer, jwtAccessTokenConverter));
        services.setTokenEnhancer(tokenEnhancerChain);

        services.setAccessTokenValiditySeconds(7200);   //令牌默认有效期2小时
        services.setRefreshTokenValiditySeconds(259200);    //刷新令牌默认有效期3天
        return services;
    }

    /**
     * 设置授权码模式的授权码如何存取，暂时用内存方式
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new InMemoryAuthorizationCodeServices();
        //JdbcAuthorizationCodeServices
    }

    /**
     * 用来配置令牌端点的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")  //oauth.token_key公开
                .checkTokenAccess("permitAll()")    //是否能使用.../oauth/check_token进行公开
                .allowFormAuthenticationForClients();    //表单认证，申请令牌
    }

    /**
     * 配置客户端详情服务，客户端详情信息在这里进行初始化
     * <p>
     * clientId: 用来标识客户的ID。必须
     * secret: 客户端安全码，如果有的话。在微信登陆中是必须的
     * scope: 用来限制客户端的访问范围，如果是空（默认）的话，那么客户端拥有全部的访问范围
     * authorizedGrantTypes: 此客户端可以使用的授权类型，默认为空。在微信登陆中，只支持authorization_code这个一种
     * authorities: 此客户端可以使用的权限（基于Spring Security authorities）
     * redirectUrls: 回调地址。授权服务会往该回调地址推送此客户端相关的信息
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //模拟两个不同资源客户端
        clients.inMemory()
                .withClient("c1")   //client_id
                .secret(new BCryptPasswordEncoder().encode("secret"))   //客户端密钥
                .resourceIds("salary")   //客户拥有的资源列表，用于资源服务器中进行相应的资源权限管理
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
                .scopes("all")  //允许的授权范围
                .autoApprove(false) //跳转到授权页面
                .redirectUris("http://www.baidu.com")
                .and()
                .withClient("c2")   //client_id
                .secret(new BCryptPasswordEncoder().encode("secret"))   //客户端密钥
                .resourceIds("mobile")   //客户拥有的资源列表，用于资源服务器中进行相应的资源权限管理
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
                .scopes("all")  //允许的授权范围
                .autoApprove(false) //跳转到授权页面
                .redirectUris("http://www.baidu.com");   //回调地址
    }

    /**
     * 用来配置令牌(token)的访问端点和令牌服务(token service)
     * <p>
     * 框架默认的URL链接
     * /oauth/authorize: 授权端点
     * /oauth/token: 令牌端点
     * /oauth/confirm_access: 用户确认授权提交的端点
     * /oauth/error: 授权服务错误信息端点
     * /oauth/check_token: 用于资源服务访问的令牌进行解析的端点
     * /oauth/token_key: 使用jwt令牌需要用到的提供公有密钥的端点
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .pathMapping("/oauth/confirm_access", "/customer/confirm_access")    //定制授权同意页面
                .authenticationManager(authenticationManager)   //认证管理器，使用password（资源所有者密码）
                .userDetailsService(userDetailsService) //密码模式的用户信息管理
                .authorizationCodeServices(authorizationCodeServices)   //授权码服务，用来设置授权服务器，主要用于authorization_code授权码类型模式
                .tokenServices(tokenServices())
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }
}
